Skip to content

feat(sandbox): integrate @anthropic-ai/sandbox-runtime as outer sandbox layer#7

Merged
linuxdevel merged 13 commits intomainfrom
feat/sandbox-runtime-integration
Mar 20, 2026
Merged

feat(sandbox): integrate @anthropic-ai/sandbox-runtime as outer sandbox layer#7
linuxdevel merged 13 commits intomainfrom
feat/sandbox-runtime-integration

Conversation

@linuxdevel
Copy link
Owner

Summary

  • Adds @anthropic-ai/sandbox-runtime as the outer sandbox layer (bubblewrap on Linux, sandbox-exec on macOS), replacing the previous unshare-based approach
  • Translates SandboxPolicy to SandboxRuntimeConfig via PolicyBuilder.toRuntimeConfig(), with a denylist for credential dirs (.ssh, .aws, etc.) using lstatSync to skip symlinks (avoids bwrap failure on WSL2 paths)
  • Injects the native C helper (safeclaw-sandbox-helper) as the inner process via --policy-file <tmp> when available, enabling Landlock + seccomp + cap-drop inside the bwrap container
  • Updates doctor checks: replaces linuxCheck/unshareCheck with platformCheck (Linux+macOS) and bwrapCheck, adds socatCheck and ripgrepCheck for sandbox-runtime dependencies
  • All 693 tests pass; zero lint/type errors

Commits

Commit Description
9e92639 Add @anthropic-ai/sandbox-runtime git dependency
eff4215 Extend NetworkPolicy type and EnforcementLayers/KernelCapabilities
00753c8 Add PolicyBuilder.toRuntimeConfig()
c257041 Replace unshare detection with sandbox-runtime dependency checks
19d12a9 Rewrite Sandbox.execute() to use SandboxManager.wrapWithSandbox()
86766b2 Initialize SandboxManager network proxy before constructing Sandbox
4bfacad Update doctor checks for sandbox-runtime
67aa8dc Inject C helper via --policy-file for Landlock + cap-drop inside bwrap
2414d0f Fix: filter denyRead paths by real directories to avoid bwrap failure on WSL2 symlinks

Test Plan

  • pnpm test — 693 tests pass (63 test files)
  • pnpm lint — zero diagnostics
  • pnpm typecheck — zero errors
  • Integration tests skip gracefully when helper binary or user namespaces are unavailable
  • WSL2 symlinked credential dirs (e.g. ~/.aws → /mnt/c/...) are excluded from bwrap denyRead config

🤖 Generated with Claude Code

linuxdevel and others added 13 commits March 20, 2026 09:48
Replace linuxCheck with platformCheck (passes on linux and darwin).
Replace unshareCheck with bwrapCheck (bubblewrap is now the outer
isolation layer). Add socatCheck and ripgrepCheck for sandbox-runtime
network proxy and search tool dependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rop inside bwrap

When the native helper binary is found, Sandbox.execute() now writes the
policy (filesystem + syscalls) to a mode-0600 temp file and runs:
  helper --policy-file <tmp> -- command [args]
as the inner command inside the bwrap container. This re-enables the
Landlock filesystem restrictions and capability-drop layer (Phase 2).

enforcement.landlock and enforcement.capDrop are now true when the helper
is present. The temp file is cleaned up in both close and error handlers.
If the helper is in a non-system directory, its parent is added to
rtConfig.filesystem.allowWrite so bwrap bind-mounts it into the container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p failure

PolicyBuilder.toRuntimeConfig() used to include all sensitive home dirs
(~/.ssh, ~/.aws, etc.) in denyRead unconditionally. bwrap cannot mount
tmpfs over non-existent paths or symlinks to external filesystems (e.g.
WSL2 symlinks pointing to /mnt/c/...), causing the sandbox command to
fail with "Can't mount tmpfs on /newroot/home/...".

Fix: use lstatSync to filter denyRead to only include paths that are
real directories (not symlinks, not missing). Symlinks to Windows paths
on WSL2 are excluded. Update sandbox.test.ts and policy-builder.test.ts
mocks to cover the new lstatSync import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- README: add macOS to platform requirements; add bubblewrap/socat
  prerequisites; update sandbox description and @safeclaw/sandbox
  blurb; mark Feature 5 (sandbox-runtime integration) as Done
- AGENTS.md: update platform note (Linux + macOS); rewrite Sandbox
  section to reflect SandboxManager/wrapWithSandbox outer layer and
  --policy-file inner protocol; add SandboxManager.initialize() to
  bootstrap flow
- docs/getting-started.md: add bubblewrap/socat to prerequisites;
  replace kernel-only verification with bwrap/socat checks; update
  doctor command category description
- docs/sandboxing.md: rewrite architecture diagram to show two-layer
  model (sandbox-runtime outer + C helper inner); update Layer 1 from
  unshare to sandbox-runtime/bwrap; document PolicyBuilder.toRuntimeConfig
  and WSL2 symlink handling; update policy delivery from fd 3 to
  --policy-file; add pivotRoot/bindMounts to EnforcementLayers; update
  graceful degradation table; update platform requirements section
- docs/security-model.md: rewrite sandboxing architecture intro for
  two-layer model; update namespaces section to reference bwrap; fix
  development policy (remove ~/.safeclaw from readwrite — it was never
  included; home dir is read-only)
- docs/architecture.md: update @safeclaw/sandbox description; update
  Linux-only design decision to Linux + macOS; update doctor description

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bootstrap.ts imports SandboxManager directly from @anthropic-ai/sandbox-runtime
but the package was only declared as a dependency of @safeclaw/sandbox, not
@safeclaw/cli, causing a TS2307 build error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GitHub's device flow returns 'slow_down' when the client polls too
frequently. The previous code threw 'Token poll error: slow_down',
which looked like an immediate failure during onboarding.

- Handle slow_down by increasing the interval by 5 s (as required by
  the GitHub Device Flow spec) and continuing to poll
- Add AbortSignal.timeout(30s) to requestDeviceCode and pollForToken
  fetch calls (consistent with CopilotClient.REQUEST_TIMEOUT_MS)
- Update existing fetch call assertions to expect AbortSignal
- Add test: slow_down increases interval and retries successfully

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getCopilotToken had no AbortSignal, causing an indefinite hang after
the GitHub device flow poll succeeded but the Copilot API token
exchange stalled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@linuxdevel linuxdevel merged commit 7310815 into main Mar 20, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant